Vincent Bernat: Build-time dependency patching for Android
This post shows how to patch an external dependency for an
Android project at build-time with Gradle. This leverages
the Transform API and Javassist, a Java bytecode manipulation tool.
Disclaimer: I am not a seasoned Android programmer, so take this
with a grain of salt.
Context
This section adds some context to the example. Feel free to skip it.
Dashkiosk is an application to manage dashboards on many
displays. It provides an Android application you can install on one of
those cheap Android sticks. Under the table, the application is an
embedded webview backed by the Crosswalk Project web runtime
which brings an up-to-date web engine, even for older versions of
Android1.
Recently, a security vulnerability has been spotted in how invalid
certificates were handled. When a certificate cannot be verified, the
webview defers the decision to the host application by calling
the
buildscript dependencies classpath 'com.android.tools.build:gradle:2.2.+' classpath 'com.android.tools.build:transform-api:1.5.+' classpath 'org.javassist:javassist:3.21.+' classpath 'commons-io:commons-io:2.4'
Context
This section adds some context to the example. Feel free to skip it.
Dashkiosk is an application to manage dashboards on many
displays. It provides an Android application you can install on one of
those cheap Android sticks. Under the table, the application is an
embedded webview backed by the Crosswalk Project web runtime
which brings an up-to-date web engine, even for older versions of
Android1.
Recently, a security vulnerability has been spotted in how invalid
certificates were handled. When a certificate cannot be verified, the
webview defers the decision to the host application by calling
the onReceivedSslError()
method:
Notify the host application that an SSL error occurred while loading
a resource. The host application must call either
callback.onReceiveValue(true)
or
callback.onReceiveValue(false)
. Note that the decision may be
retained for use in response to future SSL errors. The default
behavior is to pop up a dialog.
The default behavior is specific to Crosswalk webview: the Android
builtin one just cancels the load. Unfortunately,
the fix applied by Crosswalk is different and, as a side effect,
the onReceivedSslError()
method is not invoked anymore2.
Dashkiosk comes with an option to ignore TLS errors3. The
mentioned security fix breaks this feature. The following example will
demonstrate how to patch Crosswalk to recover the previous
behavior4.
Simple method replacement
Let s replace the shouldDenyRequest()
method
from the org.xwalk.core.internal.SslUtil
class with this version:
// In SslUtil class
public static boolean shouldDenyRequest(int error)
return false;
Transform registration
Gradle Transform API enables the manipulation of compiled class
files before they are converted to DEX files. To declare a transform
and register it, include the following code in your build.gradle
:
import com.android.build.api.transform.Context
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformOutputProvider
import org.gradle.api.logging.Logger
class PatchXWalkTransform extends Transform
Logger logger = null;
public PatchXWalkTransform(Logger logger)
this.logger = logger
@Override
String getName()
return "PatchXWalk"
@Override
Set<QualifiedContent.ContentType> getInputTypes()
return Collections.singleton(QualifiedContent.DefaultContentType.CLASSES)
@Override
Set<QualifiedContent.Scope> getScopes()
return Collections.singleton(QualifiedContent.Scope.EXTERNAL_LIBRARIES)
@Override
boolean isIncremental()
return true
@Override
void transform(Context context,
Collection<TransformInput> inputs,
Collection<TransformInput> referencedInputs,
TransformOutputProvider outputProvider,
boolean isIncremental) throws IOException, TransformException, InterruptedException
// We should do something here
// Register the transform
android.registerTransform(new PatchXWalkTransform(logger))
The getInputTypes()
method should return the set of types of data
consumed by the transform. In our case, we want to transform
classes. Another possibility is to transform resources.
The getScopes()
method should return a set of scopes for the
transform. In our case, we are only interested by the external
libraries. It s also possible to transform our own classes.
The isIncremental()
method returns true because we support
incremental builds.
The transform()
method is expected to take all the provided inputs
and copy them (with or without modifications) to the location supplied
by the output provider. We didn t implement this method yet. This
causes the removal of all external dependencies from the application.
Noop transform
To keep all external dependencies unmodified, we must copy them:
@Override
void transform(Context context,
Collection<TransformInput> inputs,
Collection<TransformInput> referencedInputs,
TransformOutputProvider outputProvider,
boolean isIncremental) throws IOException, TransformException, InterruptedException
inputs.each
it.jarInputs.each
def jarName = it.name
def src = it.getFile()
def dest = outputProvider.getContentLocation(jarName,
it.contentTypes, it.scopes,
Format.JAR);
def status = it.getStatus()
if (status == Status.REMOVED) //
logger.info("Remove $ src ")
FileUtils.delete(dest)
else if (!isIncremental status != Status.NOTCHANGED) //
logger.info("Copy $ src ")
FileUtils.copyFile(src, dest)
We also need two additional imports:
import com.android.build.api.transform.Status
import org.apache.commons.io.FileUtils
Since we are handling external dependencies, we only have to manage
JAR files. Therefore, we only iterate on jarInputs
and not on
directoryInputs
. There are two cases when handling incremental build:
either the file has been removed ( ) or it has been modified ( ). In
all other cases, we can safely assume the file is already correctly copied.
JAR patching
When the external dependency is the Crosswalk JAR file, we also need
to modify it. Here is the first part of the code (replacing ):
if ("$ src " ==~ ".*/org.xwalk/xwalk_core.*/classes.jar")
def pool = new ClassPool()
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil') //
def ctm = ctc.getDeclaredMethod('shouldDenyRequest')
ctc.removeMethod(ctm) //
ctc.addMethod(CtNewMethod.make("""
public static boolean shouldDenyRequest(int error)
return false;
""", ctc)) //
def sslUtilBytecode = ctc.toBytecode() //
// Write back the JAR file
//
else
logger.info("Copy $ src ")
FileUtils.copyFile(src, dest)
We also need the following additional imports to use Javassist:
import javassist.ClassPath
import javassist.ClassPool
import javassist.CtNewMethod
Once we have located the JAR file we want to modify, we add it to our
classpath and retrieve the class we are interested in ( ). We
locate the appropriate method and delete it ( ). Then, we add our
custom method using the same name ( ). The whole operation is done
in memory. We retrieve the bytecode of the modified class in .
The remaining step is to rebuild the JAR file:
def input = new JarFile(src)
def output = new JarOutputStream(new FileOutputStream(dest))
//
input.entries().each
if (!it.getName().equals("org/xwalk/core/internal/SslUtil.class"))
def s = input.getInputStream(it)
output.putNextEntry(new JarEntry(it.getName()))
IOUtils.copy(s, output)
s.close()
//
output.putNextEntry(new JarEntry("org/xwalk/core/internal/SslUtil.class"))
output.write(sslUtilBytecode)
output.close()
We need the following additional imports:
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import org.apache.commons.io.IOUtils
There are two steps. In , all classes are copied to the new JAR,
except the SslUtil
class. In , the modified bytecode for SslUtil
is added to the JAR.
That s all! You can view the complete example on GitHub.
More complex method replacement
In the above example, the new method doesn t use any external
dependency. Let s suppose we also want to replace
the sslErrorFromNetErrorCode()
method
from the same class with the following one:
import org.chromium.net.NetError;
import android.net.http.SslCertificate;
import android.net.http.SslError;
// In SslUtil class
public static SslError sslErrorFromNetErrorCode(int error,
SslCertificate cert,
String url)
switch(error)
case NetError.ERR_CERT_COMMON_NAME_INVALID:
return new SslError(SslError.SSL_IDMISMATCH, cert, url);
case NetError.ERR_CERT_DATE_INVALID:
return new SslError(SslError.SSL_DATE_INVALID, cert, url);
case NetError.ERR_CERT_AUTHORITY_INVALID:
return new SslError(SslError.SSL_UNTRUSTED, cert, url);
default:
break;
return new SslError(SslError.SSL_INVALID, cert, url);
The major difference with the previous example is that we need to
import some additional classes.
Android SDK import
The classes from the Android SDK are not part of the external
dependencies. They need to be imported separately. The full path of
the JAR file is:
androidJar = "$ android.getSdkDirectory().getAbsolutePath() /platforms/" +
"$ android.getCompileSdkVersion() /android.jar"
We need to load it before adding the new method into SslUtil
class:
def pool = new ClassPool()
pool.insertClassPath(androidJar)
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil')
def ctm = ctc.getDeclaredMethod('sslErrorFromNetErrorCode')
ctc.removeMethod(ctm)
pool.importPackage('android.net.http.SslCertificate');
pool.importPackage('android.net.http.SslError');
//
External dependency import
We must also import org.chromium.net.NetError
and therefore, we need
to put the appropriate JAR in our classpath. The easiest way is to
iterate through all the external dependencies and add them to the classpath.
def pool = new ClassPool()
pool.insertClassPath(androidJar)
inputs.each
it.jarInputs.each
def jarName = it.name
def src = it.getFile()
def status = it.getStatus()
if (status != Status.REMOVED)
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil')
def ctm = ctc.getDeclaredMethod('sslErrorFromNetErrorCode')
ctc.removeMethod(ctm)
pool.importPackage('android.net.http.SslCertificate');
pool.importPackage('android.net.http.SslError');
pool.importPackage('org.chromium.net.NetError');
ctc.addMethod(CtNewMethod.make(" "))
// Then, rebuild the JAR...
Happy hacking!
-
Before Android 4.4, the webview was severely
outdated. Starting from Android 5, the webview is shipped as
a separate component with updates. Embedding Crosswalk is
still convenient as you know exactly which version you can rely
on.
-
I hope to have this fixed in later versions.
-
This may seem harmful and you are right. However, if you
have an internal CA, it is currently not possible to provide its
own trust store to a webview. Moreover, the system trust store is
not used either. You also may want to use TLS for authentication
only with client certificates, a feature supported by Dashkiosk.
-
Crosswalk being an opensource project, an
alternative would have been to patch Crosswalk source code and
recompile it. However, Crosswalk embeds Chromium and
recompiling the whole stuff consumes a lot of resources.
callback.onReceiveValue(true)
or
callback.onReceiveValue(false)
. Note that the decision may be
retained for use in response to future SSL errors. The default
behavior is to pop up a dialog.
shouldDenyRequest()
method
from the org.xwalk.core.internal.SslUtil
class with this version:
// In SslUtil class public static boolean shouldDenyRequest(int error) return false;
Transform registration
Gradle Transform API enables the manipulation of compiled class
files before they are converted to DEX files. To declare a transform
and register it, include the following code in your build.gradle
:
import com.android.build.api.transform.Context
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformException
import com.android.build.api.transform.TransformInput
import com.android.build.api.transform.TransformOutputProvider
import org.gradle.api.logging.Logger
class PatchXWalkTransform extends Transform
Logger logger = null;
public PatchXWalkTransform(Logger logger)
this.logger = logger
@Override
String getName()
return "PatchXWalk"
@Override
Set<QualifiedContent.ContentType> getInputTypes()
return Collections.singleton(QualifiedContent.DefaultContentType.CLASSES)
@Override
Set<QualifiedContent.Scope> getScopes()
return Collections.singleton(QualifiedContent.Scope.EXTERNAL_LIBRARIES)
@Override
boolean isIncremental()
return true
@Override
void transform(Context context,
Collection<TransformInput> inputs,
Collection<TransformInput> referencedInputs,
TransformOutputProvider outputProvider,
boolean isIncremental) throws IOException, TransformException, InterruptedException
// We should do something here
// Register the transform
android.registerTransform(new PatchXWalkTransform(logger))
The getInputTypes()
method should return the set of types of data
consumed by the transform. In our case, we want to transform
classes. Another possibility is to transform resources.
The getScopes()
method should return a set of scopes for the
transform. In our case, we are only interested by the external
libraries. It s also possible to transform our own classes.
The isIncremental()
method returns true because we support
incremental builds.
The transform()
method is expected to take all the provided inputs
and copy them (with or without modifications) to the location supplied
by the output provider. We didn t implement this method yet. This
causes the removal of all external dependencies from the application.
Noop transform
To keep all external dependencies unmodified, we must copy them:
@Override
void transform(Context context,
Collection<TransformInput> inputs,
Collection<TransformInput> referencedInputs,
TransformOutputProvider outputProvider,
boolean isIncremental) throws IOException, TransformException, InterruptedException
inputs.each
it.jarInputs.each
def jarName = it.name
def src = it.getFile()
def dest = outputProvider.getContentLocation(jarName,
it.contentTypes, it.scopes,
Format.JAR);
def status = it.getStatus()
if (status == Status.REMOVED) //
logger.info("Remove $ src ")
FileUtils.delete(dest)
else if (!isIncremental status != Status.NOTCHANGED) //
logger.info("Copy $ src ")
FileUtils.copyFile(src, dest)
We also need two additional imports:
import com.android.build.api.transform.Status
import org.apache.commons.io.FileUtils
Since we are handling external dependencies, we only have to manage
JAR files. Therefore, we only iterate on jarInputs
and not on
directoryInputs
. There are two cases when handling incremental build:
either the file has been removed ( ) or it has been modified ( ). In
all other cases, we can safely assume the file is already correctly copied.
JAR patching
When the external dependency is the Crosswalk JAR file, we also need
to modify it. Here is the first part of the code (replacing ):
if ("$ src " ==~ ".*/org.xwalk/xwalk_core.*/classes.jar")
def pool = new ClassPool()
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil') //
def ctm = ctc.getDeclaredMethod('shouldDenyRequest')
ctc.removeMethod(ctm) //
ctc.addMethod(CtNewMethod.make("""
public static boolean shouldDenyRequest(int error)
return false;
""", ctc)) //
def sslUtilBytecode = ctc.toBytecode() //
// Write back the JAR file
//
else
logger.info("Copy $ src ")
FileUtils.copyFile(src, dest)
We also need the following additional imports to use Javassist:
import javassist.ClassPath
import javassist.ClassPool
import javassist.CtNewMethod
Once we have located the JAR file we want to modify, we add it to our
classpath and retrieve the class we are interested in ( ). We
locate the appropriate method and delete it ( ). Then, we add our
custom method using the same name ( ). The whole operation is done
in memory. We retrieve the bytecode of the modified class in .
The remaining step is to rebuild the JAR file:
def input = new JarFile(src)
def output = new JarOutputStream(new FileOutputStream(dest))
//
input.entries().each
if (!it.getName().equals("org/xwalk/core/internal/SslUtil.class"))
def s = input.getInputStream(it)
output.putNextEntry(new JarEntry(it.getName()))
IOUtils.copy(s, output)
s.close()
//
output.putNextEntry(new JarEntry("org/xwalk/core/internal/SslUtil.class"))
output.write(sslUtilBytecode)
output.close()
We need the following additional imports:
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import org.apache.commons.io.IOUtils
There are two steps. In , all classes are copied to the new JAR,
except the SslUtil
class. In , the modified bytecode for SslUtil
is added to the JAR.
That s all! You can view the complete example on GitHub.
More complex method replacement
In the above example, the new method doesn t use any external
dependency. Let s suppose we also want to replace
the sslErrorFromNetErrorCode()
method
from the same class with the following one:
import org.chromium.net.NetError;
import android.net.http.SslCertificate;
import android.net.http.SslError;
// In SslUtil class
public static SslError sslErrorFromNetErrorCode(int error,
SslCertificate cert,
String url)
switch(error)
case NetError.ERR_CERT_COMMON_NAME_INVALID:
return new SslError(SslError.SSL_IDMISMATCH, cert, url);
case NetError.ERR_CERT_DATE_INVALID:
return new SslError(SslError.SSL_DATE_INVALID, cert, url);
case NetError.ERR_CERT_AUTHORITY_INVALID:
return new SslError(SslError.SSL_UNTRUSTED, cert, url);
default:
break;
return new SslError(SslError.SSL_INVALID, cert, url);
The major difference with the previous example is that we need to
import some additional classes.
Android SDK import
The classes from the Android SDK are not part of the external
dependencies. They need to be imported separately. The full path of
the JAR file is:
androidJar = "$ android.getSdkDirectory().getAbsolutePath() /platforms/" +
"$ android.getCompileSdkVersion() /android.jar"
We need to load it before adding the new method into SslUtil
class:
def pool = new ClassPool()
pool.insertClassPath(androidJar)
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil')
def ctm = ctc.getDeclaredMethod('sslErrorFromNetErrorCode')
ctc.removeMethod(ctm)
pool.importPackage('android.net.http.SslCertificate');
pool.importPackage('android.net.http.SslError');
//
External dependency import
We must also import org.chromium.net.NetError
and therefore, we need
to put the appropriate JAR in our classpath. The easiest way is to
iterate through all the external dependencies and add them to the classpath.
def pool = new ClassPool()
pool.insertClassPath(androidJar)
inputs.each
it.jarInputs.each
def jarName = it.name
def src = it.getFile()
def status = it.getStatus()
if (status != Status.REMOVED)
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil')
def ctm = ctc.getDeclaredMethod('sslErrorFromNetErrorCode')
ctc.removeMethod(ctm)
pool.importPackage('android.net.http.SslCertificate');
pool.importPackage('android.net.http.SslError');
pool.importPackage('org.chromium.net.NetError');
ctc.addMethod(CtNewMethod.make(" "))
// Then, rebuild the JAR...
Happy hacking!
-
Before Android 4.4, the webview was severely
outdated. Starting from Android 5, the webview is shipped as
a separate component with updates. Embedding Crosswalk is
still convenient as you know exactly which version you can rely
on.
-
I hope to have this fixed in later versions.
-
This may seem harmful and you are right. However, if you
have an internal CA, it is currently not possible to provide its
own trust store to a webview. Moreover, the system trust store is
not used either. You also may want to use TLS for authentication
only with client certificates, a feature supported by Dashkiosk.
-
Crosswalk being an opensource project, an
alternative would have been to patch Crosswalk source code and
recompile it. However, Crosswalk embeds Chromium and
recompiling the whole stuff consumes a lot of resources.
import com.android.build.api.transform.Context import com.android.build.api.transform.QualifiedContent import com.android.build.api.transform.Transform import com.android.build.api.transform.TransformException import com.android.build.api.transform.TransformInput import com.android.build.api.transform.TransformOutputProvider import org.gradle.api.logging.Logger class PatchXWalkTransform extends Transform Logger logger = null; public PatchXWalkTransform(Logger logger) this.logger = logger @Override String getName() return "PatchXWalk" @Override Set<QualifiedContent.ContentType> getInputTypes() return Collections.singleton(QualifiedContent.DefaultContentType.CLASSES) @Override Set<QualifiedContent.Scope> getScopes() return Collections.singleton(QualifiedContent.Scope.EXTERNAL_LIBRARIES) @Override boolean isIncremental() return true @Override void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException // We should do something here // Register the transform android.registerTransform(new PatchXWalkTransform(logger))
@Override void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException inputs.each it.jarInputs.each def jarName = it.name def src = it.getFile() def dest = outputProvider.getContentLocation(jarName, it.contentTypes, it.scopes, Format.JAR); def status = it.getStatus() if (status == Status.REMOVED) // logger.info("Remove $ src ") FileUtils.delete(dest) else if (!isIncremental status != Status.NOTCHANGED) // logger.info("Copy $ src ") FileUtils.copyFile(src, dest)
import com.android.build.api.transform.Status import org.apache.commons.io.FileUtils
jarInputs
and not on
directoryInputs
. There are two cases when handling incremental build:
either the file has been removed ( ) or it has been modified ( ). In
all other cases, we can safely assume the file is already correctly copied.
JAR patching
When the external dependency is the Crosswalk JAR file, we also need
to modify it. Here is the first part of the code (replacing ):
if ("$ src " ==~ ".*/org.xwalk/xwalk_core.*/classes.jar")
def pool = new ClassPool()
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil') //
def ctm = ctc.getDeclaredMethod('shouldDenyRequest')
ctc.removeMethod(ctm) //
ctc.addMethod(CtNewMethod.make("""
public static boolean shouldDenyRequest(int error)
return false;
""", ctc)) //
def sslUtilBytecode = ctc.toBytecode() //
// Write back the JAR file
//
else
logger.info("Copy $ src ")
FileUtils.copyFile(src, dest)
We also need the following additional imports to use Javassist:
import javassist.ClassPath
import javassist.ClassPool
import javassist.CtNewMethod
Once we have located the JAR file we want to modify, we add it to our
classpath and retrieve the class we are interested in ( ). We
locate the appropriate method and delete it ( ). Then, we add our
custom method using the same name ( ). The whole operation is done
in memory. We retrieve the bytecode of the modified class in .
The remaining step is to rebuild the JAR file:
def input = new JarFile(src)
def output = new JarOutputStream(new FileOutputStream(dest))
//
input.entries().each
if (!it.getName().equals("org/xwalk/core/internal/SslUtil.class"))
def s = input.getInputStream(it)
output.putNextEntry(new JarEntry(it.getName()))
IOUtils.copy(s, output)
s.close()
//
output.putNextEntry(new JarEntry("org/xwalk/core/internal/SslUtil.class"))
output.write(sslUtilBytecode)
output.close()
We need the following additional imports:
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import org.apache.commons.io.IOUtils
There are two steps. In , all classes are copied to the new JAR,
except the SslUtil
class. In , the modified bytecode for SslUtil
is added to the JAR.
That s all! You can view the complete example on GitHub.
More complex method replacement
In the above example, the new method doesn t use any external
dependency. Let s suppose we also want to replace
the sslErrorFromNetErrorCode()
method
from the same class with the following one:
import org.chromium.net.NetError;
import android.net.http.SslCertificate;
import android.net.http.SslError;
// In SslUtil class
public static SslError sslErrorFromNetErrorCode(int error,
SslCertificate cert,
String url)
switch(error)
case NetError.ERR_CERT_COMMON_NAME_INVALID:
return new SslError(SslError.SSL_IDMISMATCH, cert, url);
case NetError.ERR_CERT_DATE_INVALID:
return new SslError(SslError.SSL_DATE_INVALID, cert, url);
case NetError.ERR_CERT_AUTHORITY_INVALID:
return new SslError(SslError.SSL_UNTRUSTED, cert, url);
default:
break;
return new SslError(SslError.SSL_INVALID, cert, url);
The major difference with the previous example is that we need to
import some additional classes.
Android SDK import
The classes from the Android SDK are not part of the external
dependencies. They need to be imported separately. The full path of
the JAR file is:
androidJar = "$ android.getSdkDirectory().getAbsolutePath() /platforms/" +
"$ android.getCompileSdkVersion() /android.jar"
We need to load it before adding the new method into SslUtil
class:
def pool = new ClassPool()
pool.insertClassPath(androidJar)
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil')
def ctm = ctc.getDeclaredMethod('sslErrorFromNetErrorCode')
ctc.removeMethod(ctm)
pool.importPackage('android.net.http.SslCertificate');
pool.importPackage('android.net.http.SslError');
//
External dependency import
We must also import org.chromium.net.NetError
and therefore, we need
to put the appropriate JAR in our classpath. The easiest way is to
iterate through all the external dependencies and add them to the classpath.
def pool = new ClassPool()
pool.insertClassPath(androidJar)
inputs.each
it.jarInputs.each
def jarName = it.name
def src = it.getFile()
def status = it.getStatus()
if (status != Status.REMOVED)
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil')
def ctm = ctc.getDeclaredMethod('sslErrorFromNetErrorCode')
ctc.removeMethod(ctm)
pool.importPackage('android.net.http.SslCertificate');
pool.importPackage('android.net.http.SslError');
pool.importPackage('org.chromium.net.NetError');
ctc.addMethod(CtNewMethod.make(" "))
// Then, rebuild the JAR...
Happy hacking!
-
Before Android 4.4, the webview was severely
outdated. Starting from Android 5, the webview is shipped as
a separate component with updates. Embedding Crosswalk is
still convenient as you know exactly which version you can rely
on.
-
I hope to have this fixed in later versions.
-
This may seem harmful and you are right. However, if you
have an internal CA, it is currently not possible to provide its
own trust store to a webview. Moreover, the system trust store is
not used either. You also may want to use TLS for authentication
only with client certificates, a feature supported by Dashkiosk.
-
Crosswalk being an opensource project, an
alternative would have been to patch Crosswalk source code and
recompile it. However, Crosswalk embeds Chromium and
recompiling the whole stuff consumes a lot of resources.
if ("$ src " ==~ ".*/org.xwalk/xwalk_core.*/classes.jar") def pool = new ClassPool() pool.insertClassPath("$ src ") def ctc = pool.get('org.xwalk.core.internal.SslUtil') // def ctm = ctc.getDeclaredMethod('shouldDenyRequest') ctc.removeMethod(ctm) // ctc.addMethod(CtNewMethod.make(""" public static boolean shouldDenyRequest(int error) return false; """, ctc)) // def sslUtilBytecode = ctc.toBytecode() // // Write back the JAR file // else logger.info("Copy $ src ") FileUtils.copyFile(src, dest)
import javassist.ClassPath import javassist.ClassPool import javassist.CtNewMethod
def input = new JarFile(src) def output = new JarOutputStream(new FileOutputStream(dest)) // input.entries().each if (!it.getName().equals("org/xwalk/core/internal/SslUtil.class")) def s = input.getInputStream(it) output.putNextEntry(new JarEntry(it.getName())) IOUtils.copy(s, output) s.close() // output.putNextEntry(new JarEntry("org/xwalk/core/internal/SslUtil.class")) output.write(sslUtilBytecode) output.close()
import java.util.jar.JarEntry import java.util.jar.JarFile import java.util.jar.JarOutputStream import org.apache.commons.io.IOUtils
sslErrorFromNetErrorCode()
method
from the same class with the following one:
import org.chromium.net.NetError; import android.net.http.SslCertificate; import android.net.http.SslError; // In SslUtil class public static SslError sslErrorFromNetErrorCode(int error, SslCertificate cert, String url) switch(error) case NetError.ERR_CERT_COMMON_NAME_INVALID: return new SslError(SslError.SSL_IDMISMATCH, cert, url); case NetError.ERR_CERT_DATE_INVALID: return new SslError(SslError.SSL_DATE_INVALID, cert, url); case NetError.ERR_CERT_AUTHORITY_INVALID: return new SslError(SslError.SSL_UNTRUSTED, cert, url); default: break; return new SslError(SslError.SSL_INVALID, cert, url);
Android SDK import
The classes from the Android SDK are not part of the external
dependencies. They need to be imported separately. The full path of
the JAR file is:
androidJar = "$ android.getSdkDirectory().getAbsolutePath() /platforms/" +
"$ android.getCompileSdkVersion() /android.jar"
We need to load it before adding the new method into SslUtil
class:
def pool = new ClassPool()
pool.insertClassPath(androidJar)
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil')
def ctm = ctc.getDeclaredMethod('sslErrorFromNetErrorCode')
ctc.removeMethod(ctm)
pool.importPackage('android.net.http.SslCertificate');
pool.importPackage('android.net.http.SslError');
//
External dependency import
We must also import org.chromium.net.NetError
and therefore, we need
to put the appropriate JAR in our classpath. The easiest way is to
iterate through all the external dependencies and add them to the classpath.
def pool = new ClassPool()
pool.insertClassPath(androidJar)
inputs.each
it.jarInputs.each
def jarName = it.name
def src = it.getFile()
def status = it.getStatus()
if (status != Status.REMOVED)
pool.insertClassPath("$ src ")
def ctc = pool.get('org.xwalk.core.internal.SslUtil')
def ctm = ctc.getDeclaredMethod('sslErrorFromNetErrorCode')
ctc.removeMethod(ctm)
pool.importPackage('android.net.http.SslCertificate');
pool.importPackage('android.net.http.SslError');
pool.importPackage('org.chromium.net.NetError');
ctc.addMethod(CtNewMethod.make(" "))
// Then, rebuild the JAR...
Happy hacking!
-
Before Android 4.4, the webview was severely
outdated. Starting from Android 5, the webview is shipped as
a separate component with updates. Embedding Crosswalk is
still convenient as you know exactly which version you can rely
on.
-
I hope to have this fixed in later versions.
-
This may seem harmful and you are right. However, if you
have an internal CA, it is currently not possible to provide its
own trust store to a webview. Moreover, the system trust store is
not used either. You also may want to use TLS for authentication
only with client certificates, a feature supported by Dashkiosk.
-
Crosswalk being an opensource project, an
alternative would have been to patch Crosswalk source code and
recompile it. However, Crosswalk embeds Chromium and
recompiling the whole stuff consumes a lot of resources.
androidJar = "$ android.getSdkDirectory().getAbsolutePath() /platforms/" + "$ android.getCompileSdkVersion() /android.jar"
def pool = new ClassPool() pool.insertClassPath(androidJar) pool.insertClassPath("$ src ") def ctc = pool.get('org.xwalk.core.internal.SslUtil') def ctm = ctc.getDeclaredMethod('sslErrorFromNetErrorCode') ctc.removeMethod(ctm) pool.importPackage('android.net.http.SslCertificate'); pool.importPackage('android.net.http.SslError'); //
org.chromium.net.NetError
and therefore, we need
to put the appropriate JAR in our classpath. The easiest way is to
iterate through all the external dependencies and add them to the classpath.
def pool = new ClassPool() pool.insertClassPath(androidJar) inputs.each it.jarInputs.each def jarName = it.name def src = it.getFile() def status = it.getStatus() if (status != Status.REMOVED) pool.insertClassPath("$ src ") def ctc = pool.get('org.xwalk.core.internal.SslUtil') def ctm = ctc.getDeclaredMethod('sslErrorFromNetErrorCode') ctc.removeMethod(ctm) pool.importPackage('android.net.http.SslCertificate'); pool.importPackage('android.net.http.SslError'); pool.importPackage('org.chromium.net.NetError'); ctc.addMethod(CtNewMethod.make(" ")) // Then, rebuild the JAR...
- Before Android 4.4, the webview was severely outdated. Starting from Android 5, the webview is shipped as a separate component with updates. Embedding Crosswalk is still convenient as you know exactly which version you can rely on.
- I hope to have this fixed in later versions.
- This may seem harmful and you are right. However, if you have an internal CA, it is currently not possible to provide its own trust store to a webview. Moreover, the system trust store is not used either. You also may want to use TLS for authentication only with client certificates, a feature supported by Dashkiosk.
- Crosswalk being an opensource project, an alternative would have been to patch Crosswalk source code and recompile it. However, Crosswalk embeds Chromium and recompiling the whole stuff consumes a lot of resources.